/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.client.screen;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.daikit.daikit4gxt.client.DkMain;
import com.daikit.daikit4gxt.client.action.BaseAction;
import com.daikit.daikit4gxt.client.action.standard.BaseReloadCurrentScreenAction;
import com.daikit.daikit4gxt.client.controller.BaseMainController;
import com.daikit.daikit4gxt.client.log.BaseLogger;
import com.daikit.daikit4gxt.client.ui.UIInvalidatable;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.TabPanel;
import com.sencha.gxt.widget.core.client.container.Container;
import com.sencha.gxt.widget.core.client.event.AddEvent;
import com.sencha.gxt.widget.core.client.event.AddEvent.AddHandler;


/**
 * Methods that should be overridden to provide custom mechanism :<br>
 * - {@link #invalidateUi()}<br>
 * - {@link #onFullScreenChanged()}<br>
 * - {@link #initializeScreen(Object...)}<br>
 * - {@link #screenNeedToBeReloadedRegardlessOfSubPanels()}<br>
 * - {@link #getReloadScreenAction(boolean, Object...)}<br>
 * - {@link #onBeforeScreenShow(Screen)}<br>
 * - {@link #onBeforeScreenLeft(BaseAction)}<br>
 * - {@link #onBeforeDisconnect(BaseAction)}<br>
 * 
 * @author Thibaut CASELLI
 * 
 */
public abstract class Screen extends ContentPanel implements UIInvalidatable
{

	protected final BaseLogger log = BaseLogger.getLog(getClass());

	private boolean isFirstReloadAfterShow = true;

	/**
	 * Constructor
	 */
	public Screen()
	{
		setFillBackgroundColor(true);
		addAddHandler(new RegisterChildrenAddHandler());
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// SUB PANELS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	protected final List<ScreenSubPanel> subPanels = new ArrayList<ScreenSubPanel>();
	protected final List<UIInvalidatable> uiInvalidateables = new ArrayList<UIInvalidatable>();

	private final void registerSubPanel(final ScreenSubPanel screenSubPanel)
	{
		subPanels.add(screenSubPanel);
	}

	private class RegisterChildrenAddHandler implements AddHandler
	{
		@Override
		public void onAdd(final AddEvent event)
		{
			final Widget addedWidget = event.getWidget();
			recursiveRegisterScreenSubPanel(addedWidget);
		}
	}

	private void recursiveRegisterScreenSubPanel(final Widget widget)
	{
		if (widget instanceof UIInvalidatable && !uiInvalidateables.contains(widget))
		{
			uiInvalidateables.add((UIInvalidatable) widget);
		}

		Container container = null;
		Panel panel = null;
		if (widget instanceof Container)
		{
			container = (Container) widget;
		}
		else if (widget instanceof TabPanel)
		{
			container = ((TabPanel) widget).getContainer();
		}
		else if (widget instanceof Panel)
		{
			panel = (Panel) widget;
		}
		if (panel != null)
		{
			for (final Widget widget2 : (Panel) widget)
			{
				recursiveRegisterScreenSubPanel(widget2);
			}
		}
		else if (container != null)
		{
			container.addAddHandler(new RegisterChildrenAddHandler());
			if (container instanceof ScreenSubPanel)
			{
				final ScreenSubPanel widgetAsScreenSubPanel = (ScreenSubPanel) container;
				if (!subPanels.contains(widgetAsScreenSubPanel))
				{
					Screen.this.registerSubPanel(widgetAsScreenSubPanel);
				}
				widgetAsScreenSubPanel.setParentScreen(Screen.this);
				uiInvalidateables.remove(widgetAsScreenSubPanel);
			}
			for (final Widget widgetChild : container)
			{
				recursiveRegisterScreenSubPanel(widgetChild);
			}
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// INVALIDATE UI
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Method called by invalidateUI action and MainController.invalidateUi(force)
	 */
	public final void baseInvalidateUi()
	{
		invalidateUi();
		for (final ScreenSubPanel screenSubPanel : subPanels)
		{
			screenSubPanel.baseInvalidateUi();
		}
		final List<UIInvalidatable> toRemove = new ArrayList<UIInvalidatable>();
		for (final UIInvalidatable invalidateable : uiInvalidateables)
		{
			//			if (invalidateable instanceof Widget && !((Widget) invalidateable).isAttached())
			//			{
			//				toRemove.add(invalidateable);
			//				//				BaseLogger.debug(getClass(), "Remove UIInvalidateable : " + invalidateable);
			//			}
			//			else
			//			{
			invalidateable.invalidateUi();
			//			}
		}
		uiInvalidateables.removeAll(toRemove);
		isFirstReloadAfterShow = false;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// ON FULL SCREEN CHANGED
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Method to be overridden to provide custom behavior when the full screen status changed.
	 */
	public void onFullScreenChanged()
	{
		// Nothing done by default
	}

	/**
	 * Method called by the {@link BaseMainController} when the full screen status changed.
	 */
	public final void baseOnFullScreenChanged()
	{
		onFullScreenChanged();
		for (final ScreenSubPanel screenSubPanel : subPanels)
		{
			screenSubPanel.onFullScreenChanged();
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// INITIALIZE SCREEN ACTIONS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	public void initializeScreen(final Object... args)
	{
		// Nothing done by default
	}

	public final BaseAction<?> getBaseInitializeScreenAction(final Object... args)
	{
		final BaseAction<Void> ret = new BaseAction<Void>(DkMain.i18n().action_initialize_screen_loading_label(), true)
		{
			@Override
			protected void run()
			{
				initializeScreen(args);
				Collections.sort(subPanels);
				for (final ScreenSubPanel screenSubPanel : subPanels)
				{
					screenSubPanel.initializeScreenSubPanel(args);
				}
				onSuccess();
			}
		};
		return ret;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// RELOAD SCREEN ACTIONS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	protected boolean screenNeedToBeReloadedRegardlessOfSubPanels()
	{
		return true;
	}

	public final boolean screenOrSubPanelNeedToBeReloaded()
	{
		boolean needToBeReloaded = screenNeedToBeReloadedRegardlessOfSubPanels();
		final Iterator<ScreenSubPanel> it = subPanels.iterator();
		while (!needToBeReloaded && it.hasNext())
		{
			if (it.next().needToBeReloaded())
			{
				needToBeReloaded = true;
			}
		}
		return needToBeReloaded;
	}

	public abstract BaseAction<?> getReloadScreenAction(final boolean force, final Object... optionalArgs);

	public BaseAction<?> getEndOfReloadScreenAction(final boolean force, final Object... optionalArgs)
	{
		// Nothing done by default
		return null;
	}

	/**
	 * Method called in the {@link BaseReloadCurrentScreenAction} chain. This action chain is initiated by the
	 * {@link MainController} and is intended to provide a refresh of the current screen.
	 * 
	 * @return The {@link BaseAction} created.
	 */
	public final BaseAction<?> getBaseReloadScreenAction(final boolean force, final Object... optionalArgs)
	{
		BaseAction<?> ret = null;
		if (force || screenOrSubPanelNeedToBeReloaded())
		{
			// Create Reload action
			final List<BaseAction<?>> actionsReload = new ArrayList<BaseAction<?>>();
			final BaseAction<?> actionReloadScreen = getReloadScreenAction(force, optionalArgs);
			if (actionReloadScreen != null)
			{
				actionsReload.add(actionReloadScreen);
			}
			for (final ScreenSubPanel screenSubPanel : subPanels)
			{
				if (force || screenSubPanel.needToBeReloaded())
				{
					final BaseAction<?> actionReloadScreenSubPanel = screenSubPanel.getReloadSubPanelAction(force, optionalArgs);
					if (actionReloadScreenSubPanel != null)
					{
						actionsReload.add(actionReloadScreenSubPanel);
					}
				}
			}
			// Create chain action.
			for (final BaseAction<?> baseAction : actionsReload)
			{
				if (ret == null)
				{
					ret = baseAction;
				}
				else
				{
					ret.setLastChainAction(baseAction);
				}
			}
		}
		return ret;
	}

	/**
	 * Method called in the {@link BaseReloadCurrentScreenAction} chain. This action chain is initiated by the
	 * {@link MainController} and is intended to provide the action chain to be executed once the screen has been
	 * reloaded. This will be executed after the common invalidateUi action.
	 * 
	 * @return The {@link BaseAction} created.
	 */
	public final BaseAction<?> getBaseEndOfReloadScreenAction(final boolean force, final Object... optionalArgs)
	{
		BaseAction<?> ret = null;
		if (force || screenOrSubPanelNeedToBeReloaded())
		{
			// Create end of reload action
			final List<BaseAction<?>> actionsEndOfReload = new ArrayList<BaseAction<?>>();
			final BaseAction<?> actionEndOfReloadScreen = getEndOfReloadScreenAction(force, optionalArgs);
			if (actionEndOfReloadScreen != null)
			{
				actionsEndOfReload.add(actionEndOfReloadScreen);
			}
			for (final ScreenSubPanel screenSubPanel : subPanels)
			{
				if (force || screenSubPanel.needToBeReloaded())
				{
					final BaseAction<?> actionEndOfReloadScreenSubPanel = screenSubPanel.getEndOfReloadScreenActionForSubPanel(force,
							optionalArgs);
					if (actionEndOfReloadScreenSubPanel != null)
					{
						actionsEndOfReload.add(actionEndOfReloadScreenSubPanel);
					}
				}
			}
			for (final BaseAction<?> baseAction : actionsEndOfReload)
			{
				if (ret == null)
				{
					ret = baseAction;
				}
				else
				{
					ret.setLastChainAction(baseAction);
				}
			}
		}
		return ret;
	}

	public final boolean isFirstReloadAfterShow()
	{
		return isFirstReloadAfterShow;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// CALLBACKS BEFORE SCREEN SHOW / LEFT OR DISCONNECT
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Function called before this screen is shown. After the call to this function the mainController.currentScreen is
	 * set to this and the show screen process (layout and chain actions) is done.
	 * 
	 * @param previousDisplayedScreen
	 *           the previously displayed screen.
	 * @param optionalArgs
	 *           optional arguments
	 */
	public void onBeforeScreenShow(final Screen previousDisplayedScreen, final Object... optionalArgs)
	{
		// nothing done by default.
	}

	/**
	 * Function called when this screen is asked to be left.
	 * 
	 * @return whether or not this screen can be left. If return is false, the screen won't be left and the current
	 *         action will be stopped.
	 * @param currentAction
	 *           the current chain action. To be able to restart it after.
	 */
	public boolean onBeforeScreenLeft(final BaseAction<?> currentAction)
	{
		// nothing done by default.
		return true;
	}

	/**
	 * Function called before a refresh screen process is initiated. This method is only called if the screen refresh is
	 * initiated from the {@link BaseMainController#refreshScreen(boolean, Object...)} or
	 * {@link BaseMainController#refreshScreen()} method. Not if the refresh screen is initiated directly from the
	 * {@link BaseReloadCurrentScreenAction} inside any action chain.
	 * 
	 * @param currentRefreshAction
	 *           the current refresh screen chain action. To be able to execute it after a potential asynchronous action.
	 * @return whether or not this screen can be refreshed. If return is false, the screen won't be refreshed and the
	 *         current refresh screen action will be aborted.
	 */
	public boolean onBeforeScreenRefresh(final BaseAction<?> currentRefreshAction)
	{
		// nothing done by default
		return true;
	}

	/**
	 * Method called by the {@link BaseMainController#onScreenBeforeShow(Screen, Object...)} and not intended to be
	 * called anywhere else. Use {@link #onBeforeScreenShow(Screen)} instead.
	 * 
	 * @param previousDisplayedScreen
	 *           the previously displayed screen.
	 * @param optionalArgs
	 *           optional arguments
	 */
	public final void baseBeforeScreenShow(final Screen previousDisplayedScreen, final Object... optionalArgs)
	{
		isFirstReloadAfterShow = true;
		onBeforeScreenShow(previousDisplayedScreen, optionalArgs);
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// UTILS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@Override
	public String toString()
	{
		final StringBuffer sb = new StringBuffer(1000);
		sb.append("(" + this.getClass().getName() + ":heading=").append(getTitle());
		return sb.append(")").toString();
	}

	public void setFillBackgroundColor(final boolean fill)
	{
		if (fill)
		{
			addStyleName("fill-background-color");
		}
		else
		{
			removeStyleName("fill-background-color");
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// DISCONNECTION IN CASE OF STANDALONE APPLICATIONS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * Function called when the user want to get disconnected.
	 * 
	 * @return whether or not the user can disconnect. If return is false, the user won't disconnect and the current
	 *         action will be stopped.
	 * @param the
	 *           current chain action. To be able to restart it after.
	 */
	public boolean onBeforeDisconnect(final BaseAction<?> currentAction)
	{
		// nothing done by default.
		return true;
	}
}
